package com.unionftech.baselibrary.base

import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Looper
import android.widget.Toast
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import kotlin.system.exitProcess

class AppCrashHandler private constructor() : Thread.UncaughtExceptionHandler {
    private lateinit var context: Context
    private lateinit var infoMap: MutableMap<String, String>
    private lateinit var executors: ExecutorService
    private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US)
    private val timeFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
    private var bufferedWriter: BufferedWriter? = null

    companion object {
        @SuppressLint("StaticFieldLeak")
        private lateinit var instance: AppCrashHandler

        @Synchronized
        @JvmStatic
        fun getInstance(): AppCrashHandler {
            if (!Companion::instance.isInitialized) {
                instance = AppCrashHandler()
            }
            return instance
        }
    }

    fun init(context: Context) {
        this.context = context
        infoMap = HashMap()
        executors = Executors.newFixedThreadPool(2)
        Thread.setDefaultUncaughtExceptionHandler(this)
        collectBaseInfo()
    }

    override fun uncaughtException(t: Thread, e: Throwable) {
        //异常处理
        executors.execute {
            Looper.prepare()
            Toast.makeText(context, "很抱歉，程序出现异常!", Toast.LENGTH_SHORT).show()
            Looper.loop()
        }
        //保存异常信息
        executors.execute {
            saveException(e)
        }
        //退出程序
        Thread.sleep(2000)
        android.os.Process.killProcess(android.os.Process.myPid())
        exitProcess(1)
    }

    private fun collectBaseInfo() {
        //获取包信息
        val packageManager = context.packageManager
        packageManager?.let {
            try {
                val packageInfo =
                    it.getPackageInfo(context.packageName ?: "", PackageManager.GET_ACTIVITIES)
                val versionName = packageInfo.versionName
                val versionCode = packageInfo.versionCode
                infoMap["versionName"] = versionName
                infoMap["versionCode"] = versionCode.toString()
                infoMap["手机型号"] = Build.MODEL
                infoMap["系统版本"] = Build.VERSION.RELEASE
                infoMap["SDK版本"] = Build.VERSION.SDK_INT.toString()
            } catch (_: Exception) {
            }
        }
        //通过反射获取Build的全部参数
        val fields = Build::class.java.fields
        if (fields.isNotEmpty()) {
            fields.forEach { field ->
                field.isAccessible = true
                val value = field.get(null)
                value?.let {
                    infoMap[field.name] = it.toString()
                }
            }
        }
    }

    private fun saveException(e: Throwable) {
        val sb = StringBuilder()
        sb.append("-------------------开始收集异常信息-------------------\n")
        sb.append("crash_time: ").append(timeFormat.format(System.currentTimeMillis())).append("\n")
        infoMap.forEach {
            sb.append(it.key).append("=").append(it.value).append("\n")
        }
        val writer = StringWriter()
        val printWriter = PrintWriter(writer)
        e.printStackTrace(printWriter)
        var cause = e.cause
        while (cause != null) {
            cause.printStackTrace(printWriter)
            cause = cause.cause
        }
        printWriter.close()
        sb.append(writer.toString())
        sb.append("-------------------结束收集异常信息-------------------\n")
        //保存日志
        try {
            saveCrashInfo(sb)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private fun saveCrashInfo(sb: StringBuilder) {
        val time = dateFormat.format(Date())
        val fileName = "crash-$time.log"
        context.getExternalFilesDir(null)?.let {
            val filePath = it.absolutePath + "/log"
            val logFile = File(filePath, fileName)
            if (open(logFile)) {
                appendCrashInfo(sb.toString())
            }
        }
    }

    private fun open(file: File): Boolean {
        if (file.exists().not()) {
            try {
                val parentFile = file.parentFile
                if (parentFile != null) {
                    if (parentFile.exists().not()) {
                        parentFile.mkdirs()
                    }
                }
                file.createNewFile()
            } catch (e: Exception) {
                e.printStackTrace()
                close()
                return false
            }
        }
        try {
            bufferedWriter = BufferedWriter(FileWriter(file, true))
        } catch (e: Exception) {
            e.printStackTrace()
            close()
            return false
        }
        return true
    }

    private fun appendCrashInfo(info: String) {
        try {
            bufferedWriter?.write(info)
            bufferedWriter?.newLine()
            bufferedWriter?.flush()
        } catch (e: Exception) {
            e.printStackTrace()
            close()
        }
    }

    private fun close() {
        if (bufferedWriter != null) {
            try {
                bufferedWriter!!.close()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        bufferedWriter = null
    }

}